﻿using Abp;
using Abp.Authorization;
using Abp.Authorization.Users;
using Abp.Configuration;
using Abp.Configuration.Startup;
using Abp.Dependency;
using Abp.Domain.Repositories;
using Abp.Domain.Uow;
using Abp.EntityFrameworkCore.Repositories;
using Abp.MultiTenancy;
using Abp.Runtime.Caching;
using Abp.Zero.Configuration;
using ARchGL.Platform.Authorization.Roles;
using ARchGL.Platform.Authorization.Users;
using ARchGL.Platform.Core;
using ARchGL.Platform.EntityFrameworkCore;
using ARchGL.Platform.MultiTenancy;
using ARchGL.Platform.Utils;
using ARchGL.Platform.Web.Authentication.JwtBearer;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;

namespace ARchGL.Platform.Authorization
{
    /// <summary>
    /// 登录管理
    /// </summary>
    public class LogInManager : AbpLogInManager<Tenant, Role, User>
    {
        private readonly IRepository<UserLoginAttemptExtend, long> userLoginAttemptExtendRepository;
        private readonly IHttpContextAccessor httpContextAccessor;
        private readonly ITenantCache tenantCache;
        private readonly ICacheManager cacheManager;
        private readonly IdentityOptions identityOptions;
        private readonly TokenAuthConfiguration configuration;

        public LogInManager(
            UserManager userManager,
            TokenAuthConfiguration _configuration,
            ITenantCache _tenantCache,
            ICacheManager _cacheManager,
            IOptions<IdentityOptions> _identityOptions,
            IMultiTenancyConfig multiTenancyConfig,
            IRepository<Tenant> tenantRepository,
            IUnitOfWorkManager unitOfWorkManager,
            ISettingManager settingManager,
            IRepository<UserLoginAttempt, long> userLoginAttemptRepository,
            IRepository<UserLoginAttemptExtend, long> _userLoginAttemptExtendRepository,
            IUserManagementConfig userManagementConfig,
            IIocResolver iocResolver,
            RoleManager roleManager,
            IPasswordHasher<User> passwordHasher,
            IHttpContextAccessor _httpContextAccessor,
            UserClaimsPrincipalFactory claimsPrincipalFactory)
            : base(
                  userManager,
                  multiTenancyConfig,
                  tenantRepository,
                  unitOfWorkManager,
                  settingManager,
                  userLoginAttemptRepository,
                  userManagementConfig,
                  iocResolver,
                  passwordHasher,
                  roleManager,
                  claimsPrincipalFactory)
        {
            httpContextAccessor = _httpContextAccessor;
            userLoginAttemptExtendRepository = _userLoginAttemptExtendRepository;

            configuration = _configuration;
            identityOptions = _identityOptions.Value;
            tenantCache = _tenantCache;
            cacheManager = _cacheManager;
        }

        public async Task<IEnumerable<Claim>> CreateJwtClaims(ClaimsIdentity identity, User user, TimeSpan? expiration = null)
        {
            var tokenValidityKey = Guid.NewGuid().ToString();
            var claims = identity.Claims.ToList();
            var nameIdClaim = claims.First(c => c.Type == identityOptions.ClaimsIdentity.UserIdClaimType);

            if (identityOptions.ClaimsIdentity.UserIdClaimType != JwtRegisteredClaimNames.Sub)
            {
                claims.Add(new Claim(JwtRegisteredClaimNames.Sub, nameIdClaim.Value));
            }

            var userIdentifier = new UserIdentifier(UserManager.AbpSession.TenantId, Convert.ToInt64(nameIdClaim.Value));

            claims.AddRange(new[]
            {
                new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.Now.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
                new Claim(AppConsts.TokenValidityKey, tokenValidityKey),
                new Claim(AppConsts.UserIdentifier, userIdentifier.ToUserIdentifierString())
            });

            cacheManager
                .GetCache(AppConsts.TokenValidityKey)
                .Set(tokenValidityKey, "");

            await UserManager.AddTokenValidityKeyAsync(user, tokenValidityKey,
                DateTime.UtcNow.Add(expiration ?? configuration.Expiration));

            return claims;
        }

        public string GetTenancyNameOrNull()
        {
            if (UserManager.AbpSession.TenantId.HasValue) return null;
            return tenantCache.GetOrNull(UserManager.AbpSession.TenantId.Value)?.TenancyName;
        }

        /// <summary>
        /// 更新登录类型
        /// </summary>
        /// <param name="userNameOrEmailAddress"></param>
        /// <param name="type"></param>
        /// <param name="version">移动端版本号</param>
        /// <returns></returns>
        public async Task UpdateLoginInfo(string userNameOrEmailAddress, int type, string version)
        {
            //这段代码是因为登录是ABP内部实现，Discriminator在内部赋值，不能被ABP查询到，所以需要手动更新这部分数据后才能使用
            var dbContext = userLoginAttemptExtendRepository.GetDbContext();
            var sql = $"UPDATE \"AbpUserLoginAttempts\" SET Discriminator=@UserLoginAttemptExtend WHERE Discriminator!=@UserLoginAttemptExtend";
            dbContext.Execute(sql, nameof(UserLoginAttemptExtend), nameof(UserLoginAttemptExtend));

            //_unitOfWorkManager.Current.EnableFilter("DiscriminatorFilter");
            //_unitOfWorkManager.Current.SetFilterParameter("DiscriminatorFilter", "Discriminator", "UserLoginAttempt");
            var entity = await userLoginAttemptExtendRepository.GetAll().OrderByDescending(x => x.CreationTime).Where(x => x.UserNameOrEmailAddress == userNameOrEmailAddress).FirstOrDefaultAsync();
            entity.Type = type;
            entity.Version = version;
            entity.ClientIpAddress = httpContextAccessor.GetClientIpAddress();

            await userLoginAttemptExtendRepository.UpdateAsync(entity);
        }
    }
}